﻿
using MathNet.Numerics.Integration;
using MathNet.Numerics.LinearAlgebra;
using System.Runtime.CompilerServices;
using System.Security;
using static Graph.Algorithm.Core.ParticleSwarmService;

namespace Graph.Algorithm.Core
{

    /// <summary>
    /// 粒子群算法  全局最优能力强   但是精度差
    /// 测试函数地址 ： en.wikipedia.org/wiki/Test_functions_for_optimization
    /// </summary>
    public class ParticleSwarmService
    {

        private static Random random = new Random();
        private static VectorBuilder<double> vectorBuilder = Vector<double>.Build;
        private static MatrixBuilder<double> matrixBuilder = Matrix<double>.Build;

        public List<Particle> particles = new List<Particle>();

        public static double tolarence = 0.001;

        /// <summary>
        /// 场
        /// </summary>
        public static Dictionary<Vector<double>, double> field { get; set; } = new Dictionary<Vector<double>, double>();

        public static Func<Vector<double>, double> denseField { get; set; } = (v) => { return 0; };


        public void RunAlgm(int numberOfGeneration, Func<Vector<double>, double> func)
        {
            double unit = 1d / numberOfGeneration;
            while (numberOfGeneration > 0)
            {
                numberOfGeneration--;
                double inertiaRatio = unit * numberOfGeneration;//惯性减小系数

                //team best vecotr 
                Vector<double> groupBestLocation = particles.First().currentLocation;

                Dictionary<Vector<double>, double> pairs = new Dictionary<Vector<double>, double>();

                particles.ForEach(p => pairs.Add(p.currentLocation, func(p.currentLocation)));
                groupBestLocation = pairs.OrderBy(p => p.Value).First().Key;

                foreach (var particle in particles)
                {
                    var curLoc = particle.currentLocation;
                    int dimension = curLoc.Count();
                    ///person 更新个人最佳方向
                    var personBestVelocity = (particle.FindMinPoint(curLoc) - curLoc).Normalize(dimension) * random.NextDouble() * inertiaRatio;
                    particle.personBestVelocity = personBestVelocity * tolarence;
                    //update groupVelocity 更新全队最佳方向
                    var groupBestVelocity = (groupBestLocation - curLoc).Normalize(dimension) * random.NextDouble();
                    particle.groupBestVelocity = groupBestVelocity * tolarence;

                    var currentVelocity = (particle.currentVelocity + groupBestVelocity + personBestVelocity) * tolarence * 3;
                    //update lcoation 更新个人位置
                    particle.currentLocation += currentVelocity;
                    //update curVelocity  更新个人惯性
                    particle.currentVelocity = currentVelocity * random.NextDouble() * inertiaRatio;

                }

                //历史记录
                particles.ForEach(p =>
                   p.historyLocation.Append(p.currentLocation));

            }
        }

        #region  离散场   非稠密   仅固定位置有值   故求最优判断需依赖附近点
        public ParticleSwarmService(Dictionary<Vector<double>, double> _field)
        {
            field = _field;
            var maxRange = Math.Max(
                field.Keys.Max(k => k[0]),
                field.Keys.Max(k => k[1]));

            for (int i = 0; i < 10; i++)
            {
                Particle particle
                    = new Particle(2, maxRange) { id = i, Name = i.ToString() };
                particles.Add(particle);
            }
        }
        public void RunDiscreteAlgm(int numberOfGeneration)
        {
            //离散场需要找到当前附近点的数据
            Func<Vector<double>, double> func = (loc) =>
            {
                ///场内距离当前粒子比较近的点集合
                var odFiled = field.OrderBy(f => (f.Key - loc).L2Norm()).ToList();
                //当前粒子周围8个中最优点
                var curLoc = odFiled.GetRange(0, 8).OrderBy(p => field[p.Key]).First().Key;
                //var curLoc = odFiled.First().Key;//场内距离当前粒子最近点  

                ///team 更新最佳位置
                //double signData = field[particle.currentLocation]; 
                var signData = field[curLoc];
                return signData;
            };

            RunAlgm(numberOfGeneration, func);

        }
        #endregion


        #region  解析连续的稠密场  任何位置都有公式可求值  
        public ParticleSwarmService(IEnumerable<double[]> ranges, double step)
        {
            for (int i = 0; i < 20; i++)
            {
                Particle particle
                    = new Particle(ranges, step) { id = i, Name = i.ToString() };
                particles.Add(particle);
            }

            //var pp1 = new Particle(ranges, step);
            //pp1.currentLocation = vectorBuilder.DenseOfArray(new double[] { ranges.First()[1], ranges.Last()[1] });
            //var pp2 = new Particle(ranges, step);
            //pp2.currentLocation = vectorBuilder.DenseOfArray(new double[] { ranges.First()[0], ranges.Last()[0] });

            //particles.Add(pp1);
            //particles.Add(pp2);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="numberOfGeneration"></param>
        /// <param name="GetDataFunc">查找当前点的数据  可使用函数计算</param>
        public void RunDenseAlgm(int numberOfGeneration)
        {
            RunAlgm(numberOfGeneration, denseField);

            //double unit = 0.9d / numberOfGeneration;
            //while (numberOfGeneration > 0)
            //{
            //    numberOfGeneration--;
            //    double inertiaRatio = unit * numberOfGeneration;//惯性减小系数

            //    //team best vecotr
            //    double value = double.MaxValue;
            //    Vector<double> groupBestLocation = particles.First().currentLocation;
            //    foreach (var particle in particles)
            //    {
            //        var curLoc = particle.currentLocation;
            //        ///team 更新全队最佳位置 
            //        var signData = denseField(curLoc);
            //        if (signData < value) { groupBestLocation = curLoc; }

            //        ///person 更新个人最佳方向
            //        particle.personBestVelocity = (curLoc - particle.currentLocation) * random.NextDouble();
            //    }
            //    //update groupVelocity 更新全队最佳方向
            //    particles.ForEach(p =>
            //        p.groupBestVelocity = (groupBestLocation - p.currentLocation) * random.NextDouble());

            //    //update lcoation 更新个人位置
            //    particles.ForEach(p =>
            //        p.currentLocation += p.currentVelocity + p.groupBestVelocity + p.personBestVelocity);

            //    //update curVelocity  更新个人惯性
            //    particles.ForEach(p =>
            //        p.currentVelocity = p.currentVelocity + p.groupBestVelocity + p.personBestVelocity);

            //    //随着迭代进行惯性减小
            //    particles.ForEach(p =>
            //        p.currentVelocity = p.currentVelocity * random.NextDouble() * inertiaRatio);

            //    //历史记录
            //    particles.ForEach(p =>
            //       p.historyLocation.Append(p.currentLocation));

            //}
        }

        #endregion


        /// <summary>
        /// 
        /// </summary>
        public class Particle
        {

            /// <summary>
            /// maxRange
            /// </summary>
            /// <param name="dimension"></param>
            /// <param name="maxRange">场的最大边界</param>
            public Particle(int dimension, double maxRange)
            {
                double[] array = new double[dimension];
                for (int i = 0; i < dimension; i++)
                {
                    array.Append(random.NextDouble() * maxRange);
                }
                array = array.Select(ar => ar * 1.00d).OrderBy(ar => random.Next()).ToArray();
                this.currentLocation = vectorBuilder.Dense(array);

                array = array.Select(ar => ar * 0.01d).OrderBy(ar => random.Next()).ToArray();
                this.currentVelocity = vectorBuilder.Dense(array);

                array = array.Select(ar => ar * 0.01d).OrderBy(ar => random.Next()).ToArray();
                this.personBestVelocity = vectorBuilder.Dense(array);

                array = array.Select(ar => ar * 0.01d).OrderBy(ar => random.Next()).ToArray();
                this.groupBestVelocity = vectorBuilder.Dense(array);

                historyLocation = new Vector<double>[dimension];
            }

            /// <summary>
            /// 各维度不同范围的初始化定义
            /// </summary>
            /// <param name="_ranges">给出各个维度的定义域  </param>
            public Particle(IEnumerable<double[]> _ranges, double step)
            {
                this.ranges = _ranges.ToList();

                this.currentLocation = vectorBuilder.Dense(GenRandomArray(_ranges, 1d));

                this.currentVelocity = vectorBuilder.Dense(GenRandomArray(_ranges, step));

                this.personBestVelocity = vectorBuilder.Dense(GenRandomArray(_ranges, step));

                this.groupBestVelocity = vectorBuilder.Dense(GenRandomArray(_ranges, step));

                historyLocation = new Vector<double>[_ranges.Count()];
            }

            private double[] GenRandomArray(IEnumerable<double[]> ranges, double ratio = 1.0d)
            {
                var dimension = ranges.Count();
                double[] array = new double[dimension];

                for (int i = 0; i < dimension; i++)
                {
                    var rand = random.NextDouble();
                    double[] range = ranges.ToList()[i];
                    array[i] = range[0] + rand * (range[1] - range[0]);
                    //array.Append();
                }
                array = array.Select(ar => ar * ratio).ToArray();
                return array;
            }

            public Vector<double> FindMinPoint(Vector<double> point)
            {
                var dim = point.Count();
                Vector<double> newPnt = vectorBuilder.Dense(dim);

                Dictionary<Vector<double>, double> pairs = new Dictionary<Vector<double>, double>();
                pairs.Add(point, denseField(point));
                for (int j = 0; j < ranges.Count; j++)
                {
                    var range = ranges[j];
                    var span = range[1] - range[0];
                    var arr = new double[dim];
                    arr[j] = 1;
                    var vec = vectorBuilder.DenseOfArray(arr);
                    Vector<double> min = point - vec * 0.02;
                    Vector<double> max = point + vec * 0.02;
                    pairs.Add(min, denseField(min));
                    pairs.Add(max, denseField(max));
                }
                newPnt = pairs.OrderBy(p => p.Value).First().Key;
                return newPnt;
            }

            public List<double[]> ranges { get; set; }

            public int id { get; set; } = 0;
            public string Name { get; set; } = string.Empty;

            public Vector<double> currentLocation { get; set; }
            public Vector<double> currentVelocity { get; set; }
            public Vector<double> personBestVelocity { get; set; }
            public Vector<double> groupBestVelocity { get; set; }

            public Vector<double>[] historyLocation { get; set; }

        }
    }

}
